• 每个进程一次最多可以有9个文件描述符
  • 重定向错误:STDERR文件描述符被设为2,可以选择只重定向错误消息,将该文件描述符放在重定向符号前,该值必须紧紧地放在重定向符号前,否则不会工作:

    1
    ls -al badfile 2> test4
    1
    2
    $cat test4
    ls: cannot access badfile: No such file or directory
  • 如果想重定向错误和正常输出,必须用两个重定向符号,需要在符号前面放上待重定向数据所对应的文件描述符,然后指向用于保存数据的输出文件:

    1
    ls -al test test2 test3 badtest 2> test6 1> test7
  • 也可以将STDERR和STDOUT的输出重定向到同一个输出文件中,可以使用特殊的重定向符号&>

    1
    ls -al test test2 test3 badtest &> test7
  • 如果有意在脚本中生成错误消息,可以将单独的一行输出重定向到STDERR,你需要做的就是使用输出重定向符来将输出消息重定向到STDERR文件描述符,在重定向到文件描述符时,必须在文件描述符数字之前加一个&

    1
    echo "This is an error message" >&2

    这样会在脚本的STDERR文件描述符所指向的位置显示文本,而不是通常的STDOUT:

    1
    2
    3
    #!/bin/bash
    echo "This is an error" >&2
    echo "This is normal output"
    1
    2
    3
    $ ./test.sh
    This is an error
    This is normal output

    默认情况下,Linux会将STDERR导向STDOUT,但是如果你在运行脚本时重定向了STDERR,脚本中所有导向STDERR的文本都会被重定向:

    1
    $ ./test.sh 2> test9

    此时错误信息就会被重定向到test9文件中,而正常信息还是会显示在显示器上

  • 如果脚本中有大量数据需要重定向,那重定向每个echo语句就会很繁琐,你可以用exec命令告诉shell脚本执行期间重定向某个特定文件描述符:

    1
    2
    3
    4
    #!/bin/bash
    exec 1>testout
    echo "This is a test of redirecting all output"
    echo "from a script to another file"
    1
    $./test.sh

    exec命令会启动一个新的shell并将STDOUT文件描述符重定向到文件,脚本中发给STDOUT的所有输出会被重定向到文件

  • 1
    2
    3
    4
    5
    6
    7
    #!/bin/bash
    exec 2>testerror
    echo "This is the start of the script"
    echo "now redirecting all output to another location"
    exec 1>testout
    echo "This output should go to the testout file"
    echo "but this should go to the testerror file" >&2
    1
    2
    3
    4
    5
    6
    7
    $ ./test.sh
    This is the start of the script
    now redirecting all output to another location
    $ cat testout
    This output should go to the testout file
    $ cat testerror
    but this should go to the testerror file
  • 可以将STDIN从键盘重定向到其他位置,exec命令允许你将STDIN重定向到Linux系统上的文件中:

    1
    exec 0< testfile

    这个命令会告诉shell从文件中获取输入而不是STDIN:

    1
    2
    3
    4
    5
    6
    7
    8
    #!/bin/bash
    echo 0< testfile
    count=1
    while read line
    do
    echo "Line #count: line"
    count=[ count + 1 ]
    done
    1
    $ ./test.sh
  • 一个进程最多可以使用9个文件描述符,除了0、1、2外另外6个都可以在shell脚本中使用,用作输入和输出重定向:

    1
    2
    3
    4
    5
    #!/bin/bash
    exec 3>testout
    echo "This should display on the monitor"
    echo "and this should be stored in the file" >&3
    echo "Then this should be back on the monitor"
    1
    2
    3
    4
    5
    $ ./test
    This should display on the monitor
    Then this should be back on the monitor
    $ cat testout
    and this should be stored in the file
  • 恢复重定向的文件描述符:

    1
    2
    3
    4
    5
    6
    7
    #!/bin/bash
    exec 3>&1
    exec 1>testout
    echo "This should store in the output file"
    echo "along with this line"
    echo 1>&3
    echo "Now things should be back to normal"
    1
    2
    3
    4
    5
    $ ./test.sh
    Now things should be back to normal
    $ cat testout
    This should store in the output file
    along with this line

    上面这段代码中首先将文件描述符3重定向到文件描述符1的当前位置,也就是STDOUT,这意味着任何发送给文件描述符3的输出都将显示在显示器上。

    第二个exec命令将STDOUT重定向到文件,shell现在将发送给STDOUT的输出直接重定向到输出文件中,但是文件描述符3仍然会指向STDOUT原来的位置,也就是显示器,如果此时将输出数据发送到文件描述符3,它仍然会显示在显示器上,尽管STDOUT已经被重定向了。

    在向STDOUT(现在指向一个文件)发送一些输出后,脚本将STDOUT重定向到文件描述符3的当前位置(现在仍然是显示器),这意味着现在STDOUT又指向了它原来的位置:显示器。

  • 创建读写文件描述符,可以用一个文件描述符对同一个文件进行读写,对同一个文件进行读写,shell会维护一个内部指针, 指明在文件中的当前位置,任何读写都会从文件指针上次的位置开始:

    1
    2
    3
    4
    5
    #!/bin/bash
    exec 3<>testfile
    read line <&3
    echo "Read: $line"
    echo "This is a test line" >&3
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    $ cat testfile
    This is the first line.
    This is the second line.
    This is the third line.
    $ ./test.sh
    cat testfile
    This is the first line.
    This is a test line
    ine.
    This is the third line.

    当脚本向文件中写入数据时,它会从文件指针所处的位置开始,read命令读取了第一行数据,所以它使得文件指针指向了第二行数据的第一个字符,在echo语句将数据输出到文件时,它会将数据放到文件指针的当前位置,覆盖了该位置的已有数据。

  • 关闭文件描述符:要关闭文件描述符,将它重定向到特殊符号&-

    1
    exec 3>&-
  • 列出打开的文件描述符:

    lsof命令会列出整个Linux系统打开的所有文件描述符,lsof命令位于/usr/sbin命令,要想以普通用户账户来运行它,必须通过全路径来引用:/usr/sbin/lsof

    lsof有几个常用的命令行选项,-p选项允许指定进程ID(PID),-d选项允许指定要显示的文件描述符编号,要想知道进程的当前PID,可以用特殊环境变量$$(shell会将它设为当前PID),-a选项用来对其他两个选项的结果进行AND运算

  • 阻止命令输出:

    通过将STDERR重定向到null文件中将全部消息删除,shell输出到null文件的任何数据都不会保存,更不会显示:

    1
    2
    ls -al > /dev/null
    ls -al badfile test16 2> /dev/null

    也可以在输入重定向中将/dev/null作为输入文件,由于/dev/null中不含有任何内容,因此通常可以用它来快速清除现有文件中的数据,而不用先删除文件再重新创建,这是清除日志文件的一个常用方法。

  • 大多数Linux发行版配置了在启动时自动删除/tmp目录的所有文件,mktemp命令可以在/tmp目录中创建一个唯一的临时文件,shell会创建这个文件,但不用默认的umask值,它会将文件的读和写权限分配给文件的属主,并将你设为文件的属主,一旦创建了文件,你就在脚本中有了完整的读写权限,但其他人无法访问(root用户除外)
  • 默认情况下,mktemp命令会在本地目录中创建一个文件,要用mktemp命令在本地目录中创建一个临时文件,你只要指定一个文件名模板就醒了,模板可以包含任意文本文件名,在文件名末尾加上6个X就行了:

    1
    $ mktemp testing.XXXXXX

    mktmp命令会用6个字符码替换这6个X,从而保证文件名在目录中是唯一的,你可以创建多个临时文件,他可以保证每个文件是唯一的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #!/bin/bash
    tempfile=$(mktemp test.XXXXXX)
    exec 3>$temp
    echo "This script writes to temp file $tempfile"
    echo "This is the first line" >&3
    echo "This is the second line" >&3
    echo "This is the last line" >&3
    exec 3>&-
    echo "Done creating temp file. The contents are:"
    cat $tempfile
    rm -f $tempfile 2> /dev/null
    1
    2
    3
    4
    5
    6
    $ ./test
    This script writes to temp file test19.vCHoya
    Done creating temp file. The contents are:
    This is the first line
    This is the second line.
    This is the last line.
  • -t选项会强制mktemp命令在系统的临时目录创建该文件,此时mktemp命令会返回用来创建临时文件的全路径,而不是只有文件名

  • -d选项会使得mktemp命令来创建一个临时目录而不是临时文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #!/bin/bash
    tempdir=$(mktemp -d dir.XXXXXX)
    cd $tempdir
    tempfile1=$(mktemp temp.XXXXXX)
    tempfile2=$(mktemp temp.XXXXXX)
    exec 7> $tempfile1
    exec 8> $tempfile2
    echo "Sending data to directory $tempdir"
    echo "This is a test line of data for $tempfile1" >&7
    echo "This is a test line of data for $tempfile2" >&8
  • tee命令相当于管道的一个T型接头,它将从STDIN过来的数据同时发往两处,一处是STDOUT,另一处是tee命令行所指定的文件名:

    1
    tee filename
    1
    $ date | tee testfile

    默认情况下,tee命令会在每次使用时覆盖输出文件内容,如果想将数据追加到文件中,必须使用-a选项:

    1
    $ date | tee -a testfile
  • 举例:读取csv格式的数据文件,输出SQL INSERT语句来将数据插入数据库:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #!/bin/bash
    outfile=members.sql
    IFS=,
    while read lname fname address city state zip
    do
    cat >> $outfile << EOF
    INSERT INTO members (lname, fname, address, city, state, zip) VALUES
    ('lname', 'fname', 'address', 'city', 'state', 'zip');
    EOF
    done < ${1}

    脚本中使用了3处重定向,while循环使用read语句来从数据文件中读取文本,这是通过在done语句中出现的重定向符号来实现的,命令行的第一个参数即members.csv文件会作为待读取数据的文件。脚本中另外两处重定向操作出现在同一条语句中:

    1
    cat >> $outfile << EOF

    这条语句中包含一个输出追加重定向和一个输入追加重定向,输出重定向将cat命令的输出追加到$outfile变量指定的文件中,cat命令的输入不再取自标准输入,而是被重定向到脚本中存储的数据,EOF符号标记了追加到文件中的数据的起止(内联输入重定向)。